package pl.donajski.magisterka;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 
 * @author Maciej Donajski
 *
 */
public class State {
	
	public State(List<Task> tasks, List<Node> nodes) {
		this.mTasks = tasks;
		this.mNodes = nodes;
		this.mValidness = 0;
		this.clear();
		this.initCost();
		this.initPenalty();
	}

	public int getCost() {
		return mCost;
	}
	
	public void move(IMove m) {
		this.mCost 	  = this.getCostAfterMove(m);
		this.mPenalty = this.getPenaltyAfterMove(m);
		this.mValidness = this.getValidnessAfterMove(m);
		m.move(this);
	}
	
	private int getValidnessAfterMove(IMove m) {
		return m.getValidnessAfterMove(this);
	}

	public int getCostAfterMove(IMove m) {
		return m.getCostAfterMove(this);
	}

	public int getPenaltyAfterMove(IMove m) {
		return m.getPenaltyAfterMove(this);
	}
	
	public List<Task> getTasks() {
		return this.mTasks;
	}
	
	public List<Node> getNodes() {
		return this.mNodes;
	}
	public int getValidness() {
		return this.mValidness;
	}
	public void setValidness(int validness) {
		this.mValidness = validness;
	}
	
	/**
	 * Synchronizes and returns opex cost of network represented by this state
	 * @return network opex cost
	 */
	public int calculateCost() {
		return this.mCost = this.getFlowsCost() + this.getProcessingCost(); 
	}
	
	/**
	 * Synchronizes flows within nodes, according to their assignments
	 */
	public void setFlows() {
		for(Node n : mNodes) {
			n.setDFlow(0);
			n.setUFlow(0);
		}
		
		this.setUflows();
		this.setDflows();
	}
	
	void printFlows() {
		for(Node n : mNodes) {
			System.out.println(n.getIndex() + ":\t" + n.getUFlow() + "\t" + n.getDFlow());
		}
	}
	
	public void fixState() {
		for(Node n : this.mNodes) {
			if(n.isValid())
				continue;
			
			for(Task t : this.mTasks) {
				if(!t.getAssignment().equals(n))
					continue;
				
				float c = Float.MAX_VALUE;
				Node best = null;
				for(Node nn : this.mNodes) {
					if(nn.equals(n))
						continue;
					if((float) nn.getCapacityModuleOpex() / nn.getCapacityModuleSize() < c && t.canAssign(nn)) {
						best = nn;
						c = (float) nn.getCapacityModuleOpex() / nn.getCapacityModuleSize();
					}
				}	
				t.setAssignment(best);
			}
		}
		
		for(Task t : this.mTasks) {
			if(t.isValid())
				continue;
			
			float c = Float.MAX_VALUE;
			Node best = null;
			for(Node nn : this.mNodes) {
				if((float) nn.getCapacityModuleOpex() / nn.getCapacityModuleSize() < c && t.canAssign(nn)) {
					best = nn;
					c = (float) nn.getCapacityModuleOpex() / nn.getCapacityModuleSize();
				}
			}	
			t.setAssignment(best);
		}
		
		this.setFlows();
		this.calculateCost();
		this.mValidness = 0;
	}
	
	
	
/* ===========================
 * ======IMPLEMENTATION=======
 * ===========================	
 */
	
	public int getProcessingCost() {
		int cost = 0;
		for(Node n : mNodes)
			cost += n.getCost();
		return cost;
	}

	public int getFlowsCost() {
		int cost = 0;
		for(Node n : mNodes) {
			int requiredModules = (int) Math.ceil( (float) Math.max( n.getDFlow(), n.getUFlow() ) / n.getCapacityModuleSize() );
			cost +=  requiredModules * n.getCapacityModuleOpex();
					
		}
		return cost;
	}
	
	public boolean isValid() {
		for(Node n : mNodes) {
			if(!n.isValid())
				return this.mIsValid = false;
		}
		
		for(Task t : mTasks) {
			if(!t.isValid())
				return this.mIsValid = false;
		}
		
		return this.mIsValid = true;
	}
	
	public int countValidness() {
		int c = 0;
		for(Node n : mNodes) {
			if(!n.isValid())
				c += n.getAssignmentsCount() - n.getProcessingRateMax();
		}
		return this.mValidness = c;
	}
	
	public int countValid() {
		int c = 0;
		for(Node n : mNodes) {
			if(!n.isValid())
				c++;
		}
		return c;
	}

	private void initPenalty() {
		this.mPenalty = 0;
	}

	private void initCost() {
		this.mCost = 0;
		
	}
	
	private void setDflows() {
		for(Task t : this.mTasks) {
			//if assignment is not a source, it needs to download input data
			if(!t.getAssignment().equals(t.getSourceNode())) {
				t.getAssignment().setDFlow(t.getAssignment().getDFlow() + t.getInputTransmissionRate());
			}
			
			//for all tasks destinations that are not equal to assignment, add output download factor
			for(Node n : t.getDestinations()) {
				if(!n.equals(t.getAssignment())) {
					n.setDFlow(n.getDFlow() + t.getOutputTransmissionRate());
				}
			}
		}
		
	}

	private void setUflows() {
		for(Task t : this.mTasks) {
			//input transmission flow
			if(!t.getSourceNode().equals(t.getAssignment())) {
				t.getSourceNode().setUFlow(t.getSourceNode().getUFlow() + t.getInputTransmissionRate());
			}
			
			//output transmission flows
			t.getAssignment().setUFlow(t.getAssignment().getUFlow() + 
					t.destinationSize() * t.getOutputTransmissionRate());
			
			//exclude itself if one of destinations
			if(t.destinationContains(t.getAssignment())) {
				t.getAssignment().setUFlow(t.getAssignment().getUFlow() - t.getOutputTransmissionRate());
			}
		}
	}
	
	private void clear() {
		for(Task t : this.mTasks) {
			t.removeAssignment();
		}
	}

	private List<Task> mTasks;
	private List<Node> mNodes;
	private boolean mIsValid;
	private int mCost;
	private int mPenalty;
	private int mValidness;
	
}
